"Utilities for loading models and the modules that contain them."

import sys
import os

__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
        'load_app', 'app_cache_ready')

class AppCache(object):
    """
    A cache that stores installed applications and their models. Used to
    provide reverse-relations and for app introspection (e.g. admin).
    """
    # Use the Borg pattern to share state between all instances. Details at
    # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531.
    __shared_state = dict(
        # Mapping of app_labels to a dictionary of model names to model code.
        app_models = {},
    )

    def __init__(self):
        self.__dict__ = self.__shared_state

    def get_models(self):
        return self.app_models.values()

    def get_model(self, model_name):
        return self.app_models.get(model_name.lower())

    def register_models(self, *models):
        """
        Register a set of models as belonging to an app.
        """
        for model in models:
            # Store as 'name: model' pair in a dictionary
            # in the _app_models dictionary
            model_name = model._meta.object_name.lower()
            if model_name in self.app_models:
                # The same model may be imported via different paths (e.g.
                # appname.models and project.appname.models). We use the source
                # filename as a means to detect identity.
                fname1 = os.path.abspath(sys.modules[model.__module__].__file__)
                fname2 = os.path.abspath(sys.modules[self.app_models[model_name].__module__].__file__)
                # Since the filename extension could be .py the first time and
                # .pyc or .pyo the second time, ignore the extension when
                # comparing.
                if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]:
                    continue
            self.app_models[model_name] = model

cache = AppCache()


# These methods were always module level, so are kept that way for backwards
# compatibility.
get_models = cache.get_models
get_model = cache.get_model
register_models = cache.register_models
